// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.eva.android;

import android.app.Application;
import android.content.Context;
import android.util.Log;

import com.x52im.rbchat.MyApplication;

import net.x52im.mobileimsdk.android.ClientCoreSDK;

import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertificateFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;

/**
 * 一个用于OkHttp的https配置的工具类。
 * <p>
 * 参考资料：
 * <pre>
 *  http://events.jianshu.io/p/4f738788be67
 *  https://blog.csdn.net/luyee2010/article/details/116276157
 *  https://blog.csdn.net/u014352886/article/details/83026047
 * </pre>
 *
 * @author AbnerMing, Jack Jiang
 * @since 8.4
 */
public class OkHttpsUtils {
    private final static String TAG = OkHttpsUtils.class.getSimpleName();

    /** 请求服务器的主机名称（请开发者自行设置） */
    private static final String HOST_NAME = "xxxxxx.com";
    /** 下载的证书的文件名称（请开发者自行设置） */
    private static String CERTIFICATE_NAME = "xxxx.crt";

    /** 需要配置给ok的SSLSocketFactory */
    private SSLSocketFactory sslSocketFactory;
    /** SSLContext对象 */
    private SSLContext sslContext;

    /** 证书管理者 */
    private MyTrustManager trustManager;

    /** 本类的全局单例 */
    private static OkHttpsUtils instance;

    /**
     * 获得本类的全局单例对象引用。
     *
     * @param appContext
     * @return
     */
    public static OkHttpsUtils getInstance(Application appContext) {
        if (instance == null) {
            synchronized (OkHttpsUtils.class) {
                if (instance == null) {
                    instance = new OkHttpsUtils(appContext);
                }
            }
        }
        return instance;
    }

    /**
     * 构造方法。
     *
     * @param appContext
     */
    private OkHttpsUtils(Application appContext) {
      initSslSocketFactory(appContext);
    }

    /**
     * 初始化。
     *
     * @param appContext
     */
    private void initSslSocketFactory(Application appContext){
        try {
            sslContext = SSLContext.getInstance("TLS");
            // 校验服务端和本地证书是否一致
            trustManager = new MyTrustManager(appContext);
            // 初始化必要的对象,固定格式直接使用即可
            sslContext.init(null, new TrustManager[]{trustManager}, new java.security.SecureRandom());

            sslSocketFactory = sslContext.getSocketFactory();

        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
        }
    }

    public HostnameVerifier getHostnameVerifier() {
        return hostnameVerifier;
    }

    public SSLSocketFactory getSslSocketFactory() {
        return sslSocketFactory;
    }

    public MyTrustManager getTrustManager() {
        return trustManager;
    }

    /**
     * X509TrustManager实现类。
     * 通过此类中的 checkServerTrusted 方法来确认服务器证书是否正确。
     */
    private static final class MyTrustManager implements X509TrustManager {
        // 客户端读取到的证书
        private X509Certificate cert;

        MyTrustManager(Application appContext) {

            // FIXME: 为了减化https的使用，此处不进行客户端证书文件的读取，建议在强安全环境下，开启下方的证书读取逻辑（前提是您得为客户端准备好证书）！
            /**
            // 从assets文件夹下根据证书名字读取证书，变成一个可用的证书对象
            this.cert = readCert(appContext, CERTIFICATE_NAME);
            **/
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // 我们在客户端只做服务器端证书校验
        }

        /**
         * 服务器端证书校验。
         *
         * @param chain 服务端返回的证书数组,因为服务器可能有多个https证书,我们在这里的逻辑就是拿到第一个证书,然后和本地证书判断,如果不一致,异常!
         */
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // FIXME: 为了减化https的使用，此处不进行证书检验，建议在强安全环境下，开启下方的代码进行检验！
            // 开启更复杂校验可进一步参考：https://blog.csdn.net/u014352886/article/details/83026047

            /**
            // 确认服务器端证书和代码中 hard code 的 CRT 证书相同，这里因为我们服务器只有一个证书，没有遍历，如果有多个,这里是for循环取出挨个判断
            if (chain[0].equals(this.cert)) {
               return;
            }
            throw new CertificateException("checkServerTrusted No trusted server cert found!");
            **/
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[]{};
        }

        /**
         * 根据asset下证书的名字取出证书,然后变成流,在变成证书对象
         */
        private X509Certificate readCert(Context context, String assetName) {
            InputStream inputStream = null;
            try {
                inputStream = context.getAssets().open(assetName);
            } catch (IOException e) {
                Log.e(TAG, e.getMessage(), e);
                return null;
            }
            X509Certificate cert = null;
            try {
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                cert = (X509Certificate) cf.generateCertificate(inputStream);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage(), e);
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (Throwable ex) {
                    Log.w(TAG, ex);
                }
            }
            return cert;
        }
    }

    /**
     * 服务器域名验证，拿到请求接口的域名和本地配的域名进行比较，如果一样返回true。
     */
    private final HostnameVerifier hostnameVerifier = new HostnameVerifier() {

        /**
         * Verify that the host name is an acceptable match with
         * the server's authentication scheme.
         *
         * @param hostname 本次请求的域名
         * @param session SSLSession used on the connection to host
         * @return true if the host name is acceptable
         */
        @Override
        public boolean verify(String hostname, SSLSession session) {
            /**
            // 如果域名是我们认可的域名，那么返回true，本次请求继续执行，返回false会中断本次请求
            return hostname.equals(HOST_NAME);
            **/

            // FIXME: 为了减化https的使用，此处不进行域名校验，建议在强安全环境下，开启上方面的代码进行检验！
            // 如果不进行校验，直接return true，代表信任所有请求
            return true;
        }
    };

    /**
     * 返回Glide需要的OkHttpClient对象。
     *
     * @return
     */
    public static OkHttpClient getOkHttpsClient4Glide() {
        //** 设置超时时间
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .writeTimeout(20, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS);

        // 进行ssl相关的配置设置
        OkHttpsUtils httpsUtils = OkHttpsUtils.getInstance(MyApplication.getInstance2());
        builder.hostnameVerifier(httpsUtils.getHostnameVerifier());
        builder.sslSocketFactory(httpsUtils.getSslSocketFactory(), httpsUtils.getTrustManager());

        OkHttpClient mOkHttpClient = builder.build();

        return mOkHttpClient;
    }
}